import re
import time

from PySide2.QtWidgets import *
from PySide2.QtGui import QTextCharFormat, QBrush, QColor, QFont
from PySide2.QtCore import QSize, QThread, Signal

"""ANSI Escape Codes
Black        0;30     Dark Gray     1;30
Red          0;31     Light Red     1;31
Green        0;32     Light Green   1;32
Brown/Orange 0;33     Yellow        1;33
Blue         0;34     Light Blue    1;34
Purple       0;35     Light Purple  1;35
Cyan         0;36     Light Cyan    1;36
Light Gray   0;37     White         1;37
"""

prefix = "\033\["
FOUND     = prefix + "[0-9;]*m"
COLOR_OFF = prefix + "0m"
RED       = prefix + "[0-9;]*31m"
GREEN     = prefix + "[0-9;]*32m"
YELLOW    = prefix + "[0-9;]*33m"
BLUE      = prefix + "[0-9;]*34m"

circle_btn_style_sheet = \
"""
background-color: rgb(0, 125, 196);
color: rgb(255, 255, 255);
border: 1px groove gray;
border-radius: 11px;
"""


class Flag(QWidget):
    valueChanged = Signal(object)

    def __init__(self, parent=None, init_val=""):
        super(Flag, self).__init__(parent)
        self._t = init_val
        self._last = init_val

    @property
    def t(self):
        return self._t

    @t.setter
    def t(self, value):
        self._last = self._t
        self._t = value
        self.valueChanged.emit(value)

    @property
    def last(self):
        return self._last


class LogConsole(QWidget):
    
    def __init__(self, parent=None, log_proc=None):
        super(LogConsole, self).__init__(parent)
        self.layout = QGridLayout(self)
        
        self.textedit = QPlainTextEdit()
        self.textedit.setReadOnly(True)
        self.textedit.setStyleSheet("background-color: rgb(20, 20, 20); color: rgb(255, 255, 255)")
        self.layout.addWidget(self.textedit, 1, 0, 1, 2)
        self.log_proc = log_proc
        self.textedit.insertPlainText("Log Console Initialized.\n")
        self.textfmt = QTextCharFormat()

        # Regex patterns
        self._pat_color_find = re.compile(FOUND)
        self._pat_color_off = re.compile(COLOR_OFF)
        self._pat_red = re.compile(RED)
        self._pat_green = re.compile(GREEN)
        self._pat_yellow = re.compile(YELLOW)
        self._pat_blue = re.compile(BLUE)

        self._color_red = QColor(255, 0, 0)
        self._color_green = QColor(0, 255, 0)
        self._color_yellow = QColor(255, 255, 0)
        self._color_white = QColor(255, 255, 255)
        self._color_blue = QColor(0, 0, 255)

        # Track lines of text
        self._lines = 0
        self._CLEAR_WHEN = 2000

    def _set_texteidt_color(self, color):
        if color == "g":
            self.textfmt.setForeground(QBrush(self._color_green))
            self.textedit.mergeCurrentCharFormat(self.textfmt)
        elif color == "r":
            self.textfmt.setForeground(QBrush(self._color_red))
            self.textedit.mergeCurrentCharFormat(self.textfmt)
        elif color == "y":
            self.textfmt.setForeground(QBrush(self._color_yellow))
            self.textedit.mergeCurrentCharFormat(self.textfmt)
        elif color == "b":
            self.textfmt.setForeground(QBrush(self._color_blue))
            self.textedit.mergeCurrentCharFormat(self.textfmt)
        elif color == "":
            self.textfmt.setForeground(QBrush(self._color_white))
            self.textedit.mergeCurrentCharFormat(self.textfmt)

    def _judge_text_color(self, text):
        if self._pat_green.match(text):
            return "g"
        elif self._pat_red.match(text):
            return "r"
        elif self._pat_yellow.match(text):
            return "y"
        elif self._pat_blue.match(text):
            return "b"
        else:
            return ""

    def insert_text(self, text):
        if self._lines >= self._CLEAR_WHEN:
            self.clear_text()
            self._lines = 0

        it_found = self._pat_color_find.finditer(text)
        cnt_found = len(self._pat_color_find.findall(text))
        text_len = len(text)

        if cnt_found == 0:
            self.textedit.appendPlainText(text)
        else:
            text_split = []
            colors = []

            prev_e, i = -1, 0
            for m in it_found:
                start, end = m.span()

                if i > 0:
                    # Text
                    text_split.append(text[prev_e:start])
                    # Color
                    colors.append(self._judge_text_color(text[start:end]))

                    # Last one
                    if i == (cnt_found - 1) and end != text_len:
                        text_split.append(text[end:])

                elif i == 0:
                    if start != 0:  # Text
                        self.textedit.appendPlainText(text[:start])
                    else:   # Color
                        colors.append(self._judge_text_color(text[start:end]))

                prev_e = end
                i += 1

                # No chars left
                if end == text_len:
                    text_split.append("")

            if len(text_split) == len(colors):
                for c, t in zip(colors, text_split):
                    self._set_texteidt_color(c)
                    if t:
                        self.textedit.appendPlainText(t)
            else:
                for t in text_split:
                    self._set_texteidt_color("")
                    if t:
                        self.textedit.appendPlainText(t)

        self._lines += 1
            
    def clear_text(self):
        self.textedit.clear()
        self.textedit.insertPlainText("Log Console Initialized.\n")

    def show_msg(self, msg):
        self.textedit.insertPlainText(msg + "\n")

    def show_notify(self, msg):
        content = "\n----------------------------------------\n" + \
                  msg + \
                  "\n----------------------------------------\n"
        self.textedit.insertPlainText(content)

class LogUpdateThread(QThread):

    def __init__(self, log_text_line, all_btns):
        super(LogUpdateThread, self).__init__()
        self._proc = None
        self._log_text_line = log_text_line
        self._all_btns = all_btns
        self._stop = False

    def run(self):
        if self._proc:
            while not self._stop and self._proc.poll() is None:
                text = self._proc.stdout.readline().decode()
                text = text.split("\n")[0]
                self._log_text_line.t = text
                time.sleep(0.001)
            
            # All buttons restore checking when building process finished
            for btn in self._all_btns:
                btn.setEnabled(True)

        self._stop = True

    @property
    def proc(self):
        return self._proc

    @proc.setter
    def proc(self, proc):
        self._proc = proc

    def stop(self):
        self._stop = True
        if self._proc.poll() is None:
            self._proc.kill()
        self.terminate()

    @property
    def stopped(self):
        return self._stop
